package com.cwz.file.utils;

import com.cwz.core.result.R;
import com.cwz.core.utils.string.StringUtils;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

/**
 * @program: w-demo
 * @description: minio 工具类
 * @author: Wen
 **/
@Slf4j
public class MinioUtils {
	/**
	 * @Description: 文件类型和文件大小校验
	 * @Author: Wen
	 * @param file:				上传的附件
	 * @param fileMaxSize:		限制上传附件的大小
	 * @param allowedFileType:	限制上传附件的类型
	 * @return: java.util.Map<java.lang.String,java.lang.Object>
	 **/
	public static R<Map<String, Object>> checkFile(MultipartFile file, Double fileMaxSize, Map<String, String> allowedFileType) {
		String fileType;
		// 文件类型判断 - 校验文件后缀
		String fileName = file.getOriginalFilename();
		if (StringUtils.isNotBlank(fileName)) {
			String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
			if (!fileTypeAllowed(suffix, allowedFileType.keySet())) {
				return R.fail("文件类型错误");
			}
		} else {
			return R.fail("文件名称错误");
		}

		// 文件类型判断 - 校验文件头内容
		try (InputStream inputStream = file.getInputStream()) {
			// 获取到上传文件的文件头信息
			String fileHeader = MinioUtils.getFileHeader(inputStream);
			if (StringUtils.isBlank(fileHeader)) {
				log.error("Failed to get file header content.");
				return R.fail("不能获取文件头信息");
			}
			// 根据上传文件的文件头获取文件的真实类型
			fileType = getFileType(fileHeader, allowedFileType);
			if (StringUtils.isBlank(fileType) || !fileTypeAllowed(fileType, allowedFileType.keySet())) {
				log.error("Unsupported file type: [{}]", fileType);
				return R.fail("文件类型不允许上传");
			}
		} catch (IOException e) {
			e.printStackTrace();
			log.error("Get file input stream failed.", e);
			return R.fail("获取输入流出错");
		}

		// 文件大小校验 - 单位：MB
		long fileBytes = file.getSize();
		double fileSize = (double) fileBytes / 1048576;
		if (fileSize <= 0) {
			return R.fail("空文件");
		} else if (fileSize > fileMaxSize) {
			return R.fail("文件大小超过限制");
		}
		Map<String, Object> map = new HashMap<>();
		map.put("fileType", fileType);
		map.put("fileSize", fileSize);
		return R.ok(map, "文件上传成功");
	}

	/**
	 * @Description: 文件类型校验
	 * @Author: Wen
	 * @param fileType: 	待校验的类型
	 * @param allowedType:  允许上传的文件类型
	 * @return: boolean
	 **/
	private static boolean fileTypeAllowed(String fileType, Set<String> allowedType) {
		if (StringUtils.isBlank(fileType) || StringUtils.isEmpty(allowedType)) {
			return false;
		}
		return allowedType.contains(fileType);
	}

	/**
	 * @Description: 据文件的头信息获取文件类型
	 * @Author: Wen
	 * @param fileHeader:		文件头信息
	 * @param allowedFileType:
	 * @return: java.lang.String
	 **/
	public static String getFileType(String fileHeader, Map<String, String> allowedFileType) {
		if (fileHeader == null || fileHeader.length() == 0) {
			return null;
		}
		fileHeader = fileHeader.toUpperCase();
		Set<String> types = allowedFileType.keySet();
		for(String type : types){
			if (fileHeader.startsWith(allowedFileType.get(type))) {
				return type;
			}
		}
		return null;
	}

	/**
	 * @Description: 文件头字节数组转为十六进制编码
	 * @Author: Wen
	 * @param content:  		文件头字节数据
	 * @return: java.lang.String
	 **/
	private static String bytesToHexString(byte[] content) {
		StringBuilder builder = new StringBuilder();
		if (content == null || content.length <= 0) {
			return null;
		}
		String temp;
		for (byte b : content) {
			temp = Integer.toHexString(b & 0xFF).toUpperCase();
			if (temp.length() < 2) {
				builder.append(0);
			}
			builder.append(temp);
		}
		return builder.toString();
	}

	/**
	 * @Description: 获取文件的文件头信息
	 * @Author: Wen
	 * @param inputStream:  	输入流
	 * @return: java.lang.String
	 **/
	private static String getFileHeader(InputStream inputStream) throws IOException {
		byte[] content = new byte[28];
		inputStream.read(content, 0, content.length);
		return bytesToHexString(content);
	}

	/**
	 * @Description: 创建 minio 客户端
	 * @Author: Wen
	 * @param url:
	 * @param accessKey:
	 * @param secretKey:
	 * @return: io.minio.MinioClient
	 **/
	public static MinioClient createMinioClient(String url, String accessKey, String secretKey) {
		return MinioClient.builder()
			.endpoint(url)
			.credentials(accessKey, secretKey)
			.build();
	}

	/**
	 * @Description: 创建一个 Bucket
	 * @Author: Wen
	 * @param mc:
	 * @param bucketName:
	 * @return: boolean
	 **/
	public static boolean makeBucket(MinioClient mc, String bucketName) {
		try {
			mc.makeBucket(MakeBucketArgs.builder()
				.bucket(bucketName)
				.build());
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	/**
	 * @Description: 上传文件
	 * @Author: Wen
	 * @param mc:
	 * @param bucket:
	 * @param filename:
	 * @param file:
	 * @return: java.lang.String
	 **/
	public static String putObject(MinioClient mc, String bucket, String filename, MultipartFile file) {
		InputStream inputStream = null;
		try {
			inputStream = file.getInputStream();
			mc.putObject(PutObjectArgs.builder()
				.bucket(bucket)
				.object(filename)
				.stream(inputStream, file.getSize(), -1)
				.contentType(file.getContentType())
				.build());
			return mc.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
				.method(Method.GET)
				.bucket(bucket)
				.object(filename)
				.build());
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				inputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}

	/**
	 * @Description: 下载文件
	 * @Author: Wen
	 * @param mc:
	 * @param bucket:
	 * @param path:
	 * @return: io.minio.GetObjectResponse
	 **/
	public static GetObjectResponse getObject(MinioClient mc, String bucket, String path) {
		try {
			return mc.getObject(GetObjectArgs.builder()
				.bucket(bucket)
				.object(path)
				.build());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * @Description: 删除文件
	 * @Author: Wen
	 * @param minioClient:
	 * @param bucket:
	 * @param path:
	 * @return: boolean
	 **/
	public static boolean remObject(MinioClient minioClient, String bucket, String path){
		try {
			minioClient.removeObject(RemoveObjectArgs.builder()
				.bucket(bucket)
				.object(path)
				.build());
			return true;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return false;
	}

	/**
	 * @Description: 批量删除文件
	 * @Author: Wen
	 * @param minioClient:
	 * @param bucket:
	 * @param filePaths:
	 * @return: boolean
	 **/
	public static boolean removeObjects(MinioClient minioClient, String bucket, List<String> filePaths){
		List<DeleteObject> objects = new ArrayList<>();
		filePaths.forEach(f -> objects.add(new DeleteObject(f)));
		Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder()
			.bucket(bucket)
			.objects(objects)
			.build());
		if (!results.iterator().hasNext()){
			return true;
		}
		for (Result<DeleteError> result : results) {
			try {
				DeleteError deleteError = result.get();
				log.error(deleteError.objectName() + ":" + deleteError.message());
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return false;
	}
}